﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using BombermanExcepciones;

namespace Bomberman
{
	[Serializable]
	public class Casillero : DependienteDelTiempo, ISerializable
	{
		public const string Arriba = "Arriba";
		public const string Abajo = "Abajo";
		public const string Izquierda = "Izquierda";
		public const string Derecha = "Derecha";

		private int posicionX;
		private int posicionY;
		private Explosion explosion = null;
		private Contenido<Posicionable> contenidoPosicionable = new Contenido<Posicionable>();
		private Contenido<Interactuable> articulos = new Contenido<Interactuable>();
		private Contenido<DependienteDelTiempo> dependientesTiempo = new Contenido<DependienteDelTiempo>();
		private Dictionary<string, Casillero> adyacentes = new Dictionary<string, Casillero>();


		public Casillero(SerializationInfo info, StreamingContext ctxt)
		{
			this.posicionX = (int)info.GetValue("PosicionX", typeof(int));
			this.posicionY = (int)info.GetValue("PosicionY", typeof(int));
			this.explosion = (Explosion)info.GetValue("Explosion", typeof(Explosion));

			Posicionable[] contenido = (Posicionable[])info.GetValue("Contenido", typeof(Posicionable[]));
			this.contenidoPosicionable.CargarDesdeArray(contenido);

			Interactuable[] articulos = (Interactuable[])info.GetValue("Articulos", typeof(Interactuable[]));
			this.articulos.CargarDesdeArray(articulos);

			DependienteDelTiempo[] dependientesDelTiempo = (DependienteDelTiempo[])info.GetValue("DependientesDelTiempo", typeof(DependienteDelTiempo[]));
			this.dependientesTiempo.CargarDesdeArray(dependientesDelTiempo);

			Casillero[] adyacentes = (Casillero[])info.GetValue("Adyacentes", typeof(Casillero[]));
			AsignarCasillerosAdyacentes(adyacentes[0], adyacentes[1], adyacentes[2], adyacentes[3]);
		}

		public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
		{
			info.AddValue("PosicionX", this.posicionX);
			info.AddValue("PosicionY", this.posicionY);
			info.AddValue("Explosion", this.explosion);
			info.AddValue("Contenido", this.contenidoPosicionable.Lista.ToArray());
			info.AddValue("Articulos", this.articulos.Lista.ToArray());
			info.AddValue("DependientesDelTiempo", this.dependientesTiempo.Lista.ToArray());
			info.AddValue("Adyacentes", TransformarAdyacentesAArray(this.adyacentes));
		}

		// Metodo usado para la serializacion 
		private Array TransformarAdyacentesAArray(Dictionary<string, Casillero> adyacentes)
		{
			Casillero[] array = new Casillero[4];

			array[0] = adyacentes[Arriba];
			array[1] = adyacentes[Abajo];
			array[2] = adyacentes[Izquierda];
			array[3] = adyacentes[Derecha];
				
			return array;
		}

		public Casillero(int posicionX = 0, int posicionY = 0)
		{             
			this.posicionX = posicionX;
			this.posicionY = posicionY;
			DefinirAdyacentesDefecto();
		}

		private void DefinirAdyacentesDefecto()
		{
			adyacentes.Add(Arriba, null);
			adyacentes.Add(Abajo, null);
			adyacentes.Add(Izquierda, null);
			adyacentes.Add(Derecha, null);
		}

		public Dictionary<string, Casillero> Adyacentes 
		{
			get { return new Dictionary<string,Casillero>(adyacentes); }
		}

		public Explosion Explosion
		{
			get { return explosion; }
		}

		public int PosicionX
		{
			get { return posicionX; }
		}

		public int PosicionY
		{
			get { return posicionY; }
		}

		public Contenido<Posicionable> ContenidoPosicionable
		{
			get { return this.contenidoPosicionable; }
		}

		public Contenido<DependienteDelTiempo> ContenidoDependienteTiempo
		{
			get { return this.dependientesTiempo; }
		}

		public Contenido<Interactuable> Articulos
		{
			get { return this.articulos; }
		}

		public void AsignarCasillerosAdyacentes(Casillero arriba , Casillero abajo, Casillero izquierda, Casillero derecha)
		{
			adyacentes[Casillero.Arriba] = arriba;
			adyacentes[Casillero.Abajo] = abajo;
			adyacentes[Casillero.Izquierda] = izquierda;
			adyacentes[Casillero.Derecha] = derecha;
		}

		public int[] VerPosicionXY()
		// Devuelve la posicion x, y del Casillero
		{
			int[] posicionXY = new int[2];
			posicionXY[0] = this.posicionX;
			posicionXY[1] = this.posicionY;
			return posicionXY;
		}

		public bool EsTransitable()
		// Devuelve true o false de acuerdo a si es o no transitable.
		{
           
			foreach (Posicionable unPosicionable in contenidoPosicionable)
			{
				if (unPosicionable.ObstruyeCamino())
				{
					return false;
				}
			}
            
			return true;
            
		}

		public void Update()
		{
			if (explosion != null) explosion.Update();

			foreach (DependienteDelTiempo objeto in dependientesTiempo)
			{
				objeto.Update();
			}
		}

		public void AgregarExplosion(Explosion explosion)
		{
			this.explosion = explosion;
		}

		public void QuitarExplosion()
		{
			this.explosion = null;
		}

		public void AgregarContenido(Posicionable unPosicionable)
		// Agrega un contenido  al casillero
		{
			contenidoPosicionable.Agregar(unPosicionable);
		}

		public void QuitarContenido(Posicionable unPosicionable)
		{
			contenidoPosicionable.Quitar(unPosicionable);
		}

		public void AgregarDependienteDelTiempo(DependienteDelTiempo dependienteTiempo)
		{
			dependientesTiempo.Agregar(dependienteTiempo);
		}

		public void QuitarDependienteDelTiempo(DependienteDelTiempo dependienteTiempo)
		{
			dependientesTiempo.Quitar(dependienteTiempo);
		}

		public void DaniarContenido(int danio)
		{
			foreach (Posicionable unPosicionable in contenidoPosicionable)
			{
				unPosicionable.RecibirAtaque(danio);
			}

		}

		internal void AgregarArticulo(Interactuable contenido)
		{
			articulos.Agregar(contenido);
		}


		// Clase Enumerable de Contenidos. Permite desde afuera iterar los contenidos de casillero y 
		// utilizar foreach.
		[Serializable]
		public class Contenido<T> : System.Collections.IEnumerable
		{
			private List<T> contenido;
			private int index;

			public List<T> Lista
			{
				get { return contenido; }
			}

			// Metodo usado para la serializacion 
			public void CargarDesdeArray(T[] array)
			{
				contenido = array.ToList<T>();
			}

			public Contenido ()
			{
				contenido = new List<T>();
				index = 0;
			}

			public void Agregar(T unElemento)
			{
				contenido.Add(unElemento);
			}

			internal void Quitar(T unElemento)
			{
				int index = contenido.IndexOf(unElemento);
				if (index < 0) return;
				
				contenido.RemoveAt(contenido.IndexOf(unElemento));
			}

			public int Contar()
			{
				return contenido.Count;
			}

			public T Current()
			{
				if (index < contenido.Count)
					return contenido[index];
				throw new IndexOutOfRangeException();
				
			}

			public bool AtEnd()
			{
				return (index == contenido.Count);
			}

			public T Next()
			{
				if (! this.AtEnd())
				index++;
				return contenido[index];
			}

			 
			public System.Collections.IEnumerator GetEnumerator()
			{
				int maxIndex = contenido.Count;
				for (int i = 0; i < maxIndex; i++)
				{
					if (i < contenido.Count)
						yield return contenido[i];
					else yield return contenido[contenido.Count - 1];	
					
				}
			}
		}
	
	}
}

